package com.google.code.avajhelpdesk.utils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public abstract class ReflectionUtils {

	public static <T> Constructor<T> findConstructorMatchingParameters(Class<T> type, Class<?>[] argClasses) {
		Class<?>[] argumentClasses = wrapPrimitiveTypes(argClasses);
		for (Constructor<?> constr : type.getConstructors()) {
			Class<?>[] contsructorParams = wrapPrimitiveTypes(constr.getParameterTypes());
			
			if (argumentTypesMatch(argumentClasses, contsructorParams)) {
				@SuppressWarnings("unchecked")
				Constructor<T> result = (Constructor<T>) constr;
				return result;
			}
		}
		
		return null;
	}

	public static <T> T newInstance(
			Constructor<T> constructor,
			Object[] initArgs) {
		
		try {
			return constructor.newInstance(initArgs);
		} catch (Exception e) {
			org.springframework.util.ReflectionUtils.handleReflectionException(e);
		}
		
		throw new AssertionError("unreacheable code");
	}
	
	public static Method findMethod(Class<?> clazz, String methodName, Class<?>[] argTypes) {
		return org.springframework.util.ReflectionUtils.findMethod(clazz, methodName, argTypes);
	}
	
	public static Object invokeMethod(Method method, Object target) {
		return org.springframework.util.ReflectionUtils.invokeMethod(method, target);
	}
	
	/**
	 * 
	 * @param argTypes types of objects, specified as arguments
	 * @param methodParameterTypes method parameter types (part of method signature)
	 * @return true if method match arguments
	 */
	private static boolean argumentTypesMatch(Class<?>[] argTypes, Class<?>[] methodParameterTypes) {
		if (argTypes.length != methodParameterTypes.length) {
			return false;
		}
		
		//Test all method parameters
		for (int i = 0; i < argTypes.length; i++) {
			//checks that method parameter is superclass or superinterface or same as argument class 
			if (!methodParameterTypes[i].isAssignableFrom(argTypes[i])) {
				return false;
			}
		}
		
		//all parameter types match argument types
		return true;
	}
	
	private static Class<?>[] wrapPrimitiveTypes(Class<?>[] sourceClasses) {
		Class<?>[] result = new Class<?>[sourceClasses.length];
		for (int i = 0; i < sourceClasses.length; i++) {
			Class<?> clazz = sourceClasses[i];
			if (clazz.isPrimitive()) {
				result[i] = getWrapperClass(clazz);
			} else {
				result[i] = clazz; 
			}
		}
		return result;
	}
	
	private static Class<?> getWrapperClass(Class<?> clazz) {
		if (!clazz.isPrimitive()) {
			return clazz;
		}
		if (clazz == Boolean.TYPE) {
			return Boolean.class; 
		}
		if (clazz == Character.TYPE) {
			return Integer.class;
		}
		if (clazz == Byte.TYPE) {
			return Byte.class;
		}
		if (clazz == Short.TYPE) {
			return Short.class;
		}
		if (clazz == Integer.TYPE) {
			return Integer.class;
		}
		if (clazz == Long.TYPE) {
			return Long.class;
		}
		if (clazz == Float.TYPE) {
			return Float.class;
		}
		if (clazz == Double.TYPE) {
			return Double.class;
		}
		if (clazz == Void.TYPE) {
			return Void.class;
		}
		throw new AssertionError("Unreacheable code");
	}

}
